package fr.upmc.dtgui.reflexion;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import fr.upmc.dtgui.annotations.IntegerActuatorData;
import fr.upmc.dtgui.annotations.IntegerRange;
import fr.upmc.dtgui.annotations.IntegerSensorData;
import fr.upmc.dtgui.annotations.RealActuatorData;
import fr.upmc.dtgui.annotations.RealRange;
import fr.upmc.dtgui.annotations.RealSensorData;
import fr.upmc.dtgui.annotations.VariationType;

/**
 * <b>TeleoperationBoardFactory est la classe qui s'occupe de générer 
 * l'interface de controle des robots</b>
 * <p>
 * Cette classe vérifie annotations du robot, instancie des variables 
 * booléennes en conséquenc
 * et créer les widgets correspondants dans les statitions de téléopérations  
 * </p>
 * <p>
 * En plus des factories pour créer différents composants des board, 
 * cette classe fait appel a différentes méthodes notament : 
 * <ul>
 * <li>makeActuatorDataSender</li>
 * <li>makeEnergyPanel</li>
 * <li>DataThreadFactory<li>
 * </ul> 
 * </p>
 * 
 * @see AnnotationParser
 * 
 * @author Ayoub Benali, Maxime Sarilar
 */

public class TeleoperationBoardFactory {

	public static CtClass createTeleoperationBoardClass(CtClass ct, 
			ClassPool pool,HashMap<String, ArrayList<Object>> annotations) 
					throws Exception 
	{
		//Création de la classe TeleoperationBoard avec ajout 
		//de la superclasse et de l'interface
		CtClass board = pool.makeClass(ct.getSimpleName()+ 
				"TeleoperationBoard");
		board.setSuperclass(pool.getCtClass("javax.swing.JPanel"));
		board.addInterface(
				pool.getCtClass("fr.upmc.dtgui.gui.RobotTeleoperationBoard"));
		
		//Création des champs nécessaires à la classe 
		CtField tgui = new CtField(pool.
				getCtClass("fr.upmc.dtgui.gui.TeleoperationGUI"), "tgui",board);
		tgui.setModifiers(Modifier.PROTECTED);
		board.addField(tgui);

		CtField lr = new CtField(pool.getCtClass("fr.upmc.dtgui.robot.Robot"),
				"lr", board);
		lr.setModifiers(Modifier.PROTECTED);
		board.addField(lr);

		//ainsi que les méthodes qu'il faut 
		CtMethod irc = new CtMethod(CtClass.booleanType, "isRobotConnected", 
				new CtClass[] {}, board);
		irc.setBody("{return $0.lr != null;}");
		board.addMethod(irc);

		
		
		// on part du principe qu'on a ne va pas générer de panel energy, 
		//speed ou energy 
		boolean hasEnergyPanel = false;
		boolean hasSpeedPanel = false;
		boolean hasSteeringPanel = false;
		
		//on initialise les variables pour sauvgarder le dataRange 
		//qu'il soit Real ou Int ainsi que la variation 
		RealRange rEnergy = null, rSpeed = null, rSteering = null;
		IntegerRange iEnergy = null, iSpeed = null, iSteering = null;
		VariationType vEnergy = null, vSpeed = null, vSteering = null;
		String energyRUnit = "";
		String speedRUnit = "";
		String steeringRUnit = "";
		String speedWUnit = "";
		String steeringWUnit = "";
		
		/*On parcourt toutes annotations type par type qu'elle soient Sensor 
		 *ou Actuator
		 * puis on met les variables précédament déclarées a true 
		 * si on trouve les annotations qu'il faut*/
		
		//on commence part le type RealSensorData
		for (Object o : annotations.get("RealSensorData")) {
			RealSensorData rsd = (RealSensorData) o;
			if (rsd.groupName().equals("energy")) {
				/*si le nom de l'annotation est energy alors il y met 
				 * la variable hasREnergyPanel a true puis on récupére 
				 * le data range ainsi que la variation
				on fait de méme pour toutes les annotations qui suivent */
				rEnergy = rsd.dataRange();			
				vEnergy = rsd.variation();
				energyRUnit = rsd.unit().name();
			} else if (rsd.groupName().equals("speed")) {
				rSpeed = rsd.dataRange();
				vSpeed = rsd.variation();
				speedRUnit = rsd.unit().name();
			} else if (rsd.groupName().equals("steering")) {
				rSteering = rsd.dataRange();
				vSteering = rsd.variation();
				steeringRUnit = rsd.unit().name();
			}
		}
		
		for (Object o : annotations.get("RealActuatorData")) {
			RealActuatorData rad = (RealActuatorData) o;
			if (rad.groupName().equals("speed")) {
				if (rSpeed == null)
					rSpeed = rad.dataRange();
				speedWUnit = rad.unit().name();
			} else if (rad.groupName().equals("steering")) {
				if (rSteering == null)
					rSteering = rad.dataRange();
				steeringWUnit = rad.unit().name();
			}
		}

		for (Object o : annotations.get("IntegerSensorData")) {
			IntegerSensorData isd = (IntegerSensorData) o;
			if (isd.groupName().equals("energy")) {
				iEnergy = isd.dataRange();
				vEnergy = isd.variation();
				energyRUnit = isd.unit().name();
			} else if (isd.groupName().equals("speed")) {
				iSpeed = isd.dataRange();
				vSpeed = isd.variation();
				speedRUnit = isd.unit().name();
			} else if (isd.groupName().equals("steering")) {
				iSteering = isd.dataRange();
				vSteering = isd.variation();
				steeringRUnit = isd.unit().name();
			}
		}
		
		for (Object o : annotations.get("IntegerActuatorData")) {
			IntegerActuatorData iad = (IntegerActuatorData) o;
			if (iad.groupName().equals("speed")) {
				if (iSpeed == null)
					iSpeed = iad.dataRange();
				speedWUnit = iad.unit().name();
			} else if (iad.groupName().equals("steering")) {
				if (iSteering == null)
					iSteering = iad.dataRange();
				steeringWUnit = iad.unit().name();
			}
		}


		
		//on prépare des portions de code vide qui vont potientiellement changer 
		String energyPart = "";
		String speedPart = "";
		String steeringPart = "";
		
		if ((AnnotationParser.hasGetSpeed || AnnotationParser.hasSetSpeed) || 
		   (AnnotationParser.hasGetSteering || AnnotationParser.hasSetSteering)) 
			// si on as au moins un speed ou un steering panel 
			//alors il faut créer le ActuatorDataSender
			DataThreadFactory.makeActuatorDataSender(board, pool);
		
		
		if (AnnotationParser.hasGetEnergy) {
			//si on doit créer un energy panel alors on appel le PanelFactory 
			//en passant les paramétres précédament récuperés 
			//(dataRange et variation)
			CtClass energyPanel = PanelFactory.makeEnergyPanel(board, pool, 
					rEnergy, iEnergy, vEnergy, energyRUnit);
			
			//ajout des champs nécessaires ainsi que des méthodes
			CtField ecv = new CtField(energyPanel, "ecv", board);
			ecv.setModifiers(Modifier.PROTECTED);
			board.addField(ecv);

			CtMethod updateEnergy = new CtMethod(CtClass.voidType,
					"updateEnergy",
					new CtClass[] { pool.getCtClass("java.lang.Object") },
					board);
			updateEnergy.setBody("{$0.ecv.updateEnergy($1) ;}");
			updateEnergy.setModifiers(Modifier.PUBLIC);
			board.addMethod(updateEnergy);

			//modification du code a injecter plus tard
			energyPart = "$0.ecv = new " + energyPanel.getName() + "() ;"
					+ "$0.add($0.ecv) ;";
		}
		
		
		String connectRobotSpeedPanelCode = "", 
			   connectRobotSteeringPanelCode = "";
		if (AnnotationParser.hasGetSpeed || AnnotationParser.hasSetSpeed) 
		{//Création du speedPanel en cas de notation sur speed 
			//création de la classe
			CtClass speedPanel = PanelFactory.makeSpeedPanel(board, pool, ct,
					AnnotationParser.hasGetSpeed, AnnotationParser.hasSetSpeed, 
					rSpeed, iSpeed, vSpeed, speedRUnit, speedWUnit);

			//création et ajout des champs et des méthodes à la classe
			CtField sp = new CtField(speedPanel, "sp", board);
			sp.setModifiers(Modifier.PROTECTED);
			board.addField(sp);
			
			CtMethod updateSpeed = new CtMethod(CtClass.voidType,
					"updateSpeed",
					new CtClass[] { pool.getCtClass("java.lang.Object") },
					board);
			updateSpeed.setBody("{$0.sp.updateSpeed($1);}");
			updateSpeed.setModifiers(Modifier.PUBLIC);
			board.addMethod(updateSpeed);
			
			//modification du code a injecter plus tard
			speedPart = "$0.sp = new " + speedPanel.getName() + "() ;"
					+ "$0.add(sp) ;";

			connectRobotSpeedPanelCode = "$0.sp.connectRobot($1) ; ";
		}

		if (AnnotationParser.hasGetSteering ||AnnotationParser.hasSetSteering){ 
			//Création du speedPanel en cas de notation sur steering 
			//création de la classe
			CtClass steeringPanel = PanelFactory.makeSteeringPanel(board, 
					pool, ct,AnnotationParser.hasGetSteering, 
					AnnotationParser.hasSetSteering, rSteering, iSteering,
					vSteering, steeringRUnit, steeringWUnit);
			
			//création et ajout des champs et des méthodes à la classe
			CtField stp = new CtField(steeringPanel, "stp", board);
			stp.setModifiers(Modifier.PROTECTED);
			board.addField(stp);
			
			CtMethod updateSteeringAngle = new CtMethod(CtClass.voidType,
					"updateSteeringAngle",
					new CtClass[] { pool.getCtClass("java.lang.Object") },
					board);
			updateSteeringAngle.setBody("{$0.stp.updateSteeringAngle($1) ;}");
			updateSteeringAngle.setModifiers(Modifier.PUBLIC);
			board.addMethod(updateSteeringAngle);
			
			
			//modification du code a injecter plus tard
			steeringPart = "$0.stp = new " + steeringPanel.getName() + "() ;"
					+ "$0.add(stp) ;";
			connectRobotSteeringPanelCode = "$0.stp.connectRobot($1) ;";
		}

		//création des méthodes necessaires au bon fonctionnement du robot 
		
		//on commence par la méthode connectRobot
		CtMethod connectRobot = new CtMethod(CtClass.voidType, "connectRobot",
				new CtClass[] { pool
						.getCtClass("fr.upmc.dtgui.robot.InstrumentedRobot") },
				board);
		connectRobot.setModifiers(Modifier.PUBLIC);
		
		//le corps de la méthodes va changer selon les annotations
		String connectRobotBody = "{" + "$0.lr = $1 ;"
				+ connectRobotSpeedPanelCode + connectRobotSteeringPanelCode
				+ "}";
		connectRobot.setBody(connectRobotBody);
		board.addMethod(connectRobot);
		
		
		//déclaration et definitation de la méthode processSensorData
		CtMethod processSensorData = new CtMethod(CtClass.voidType,
				"processSensorData",
				new CtClass[] { pool
						.getCtClass("fr.upmc.dtgui.robot.RobotStateData") },
				board);
		String processSensorDataBody = "{"
				+ "try {"
				+ "java.lang.String name = $1.getClass().getName();"
				+ "java.util.StringTokenizer token = new java.util." +
				"StringTokenizer(name, \"$\");"
				+ "token.nextToken();"
				+ "java.lang.String last = token.nextToken();"
				+ "if (last.equals(\"PositioningData\")) {"
				+ "$0.tgui.getPositionDisplay()." +
				"draw((fr.upmc.dtgui.robot.PositioningData) $1) ;"
				+ "} "
				//le corps de la méthodes va changer selon les annotations
				+ (AnnotationParser.hasGetEnergy ? ("else if " +
						"(last.equals(\"EnergyData\")) {"
						+ "$0.updateEnergy($1) ;" + "} ") : "")
				+ ((AnnotationParser.hasGetSpeed || 
						AnnotationParser.hasSetSpeed) ? 
								("else if (last.equals(\"SpeedData\")) {"
						+ "$0.updateSpeed($1) ;" + "} ") : "")
				+ ((AnnotationParser.hasGetSteering || 
						AnnotationParser.hasSetSteering) ? 
								("else if (last.equals(\"SteeringData\")) {"
						+ "$0.updateSteeringAngle($1) ;" + "}")
						: "") + "}" + "catch(Exception e) {"
				+ "e.printStackTrace();" + "}" + "}";
		processSensorData.setBody(processSensorDataBody);
		board.addMethod(processSensorData);
		
	
		//Déclaration et création du constructeur de la board
		CtConstructor boardConstructor = new CtConstructor(new CtClass[] {
				pool.getCtClass("fr.upmc.dtgui.gui.TeleoperationGUI"),
				CtClass.intType }, board);
		boardConstructor.setModifiers(Modifier.PUBLIC);

		String constructorBody = "{"
				+ "super() ;"
				+ "$0.tgui = $1 ;"
				+ "$0.setSize($2, 250) ;"
				+ "$0.setLayout(new javax.swing." +
				"BoxLayout($0, javax.swing.BoxLayout.X_AXIS)) ;"
				+ "$0.setBorder(javax.swing.BorderFactory." +
				"createLineBorder(java.awt.Color.BLACK, 2)) ;"
				+ "$0.setVisible(false) ;";
		constructorBody += energyPart + steeringPart + speedPart;
		constructorBody += "}";

		boardConstructor.setBody(constructorBody);
		board.addConstructor(boardConstructor);
		
		//création du Sensor Data Receptor grace au DataThreadFactory
		CtClass sdr = DataThreadFactory.makeSensorDataReceptor(board, pool);
		
		//ajout de la méthode makeSensorDataReceptor 
		//qui a besoin de Sensor Data Receptor
		CtMethod msdr = new CtMethod(
				pool.getCtClass("fr.upmc.dtgui.gui.SensorDataReceptorInterface"),
				"makeSensorDataReceptor", new CtClass[] {
						pool.getCtClass("fr.upmc.dtgui.gui.PositionDisplay"),
						pool.getCtClass("java.util.concurrent.BlockingQueue"),
						CtClass.intType, CtClass.intType, CtClass.intType },
				board);
		String msdrBody = "{"
				+
				sdr.getName() + " return_ = new " + sdr.getName() + "($$) ;"
				+ "return return_;" + "}";
		msdr.setBody(msdrBody);
		msdr.setModifiers(Modifier.PUBLIC);
		board.addMethod(msdr);
		
		//ajout de la méthode paintComponent
		CtMethod paintComp = new CtMethod(CtClass.voidType, "paintComponent",
				new CtClass[] { pool.get("java.awt.Graphics") }, board);
		String paintCompBody = "{"
				+ "super.paintComponent($1) ;"
				+ "$0.setSize(1000, 550) ;"
				+ "$0.setBorder(javax.swing.BorderFactory.createLineBorder" +
				"(java.awt.Color.BLACK, 2)) ;}";
		paintComp.setBody(paintCompBody);
		paintComp.setModifiers(Modifier.PROTECTED);
		board.addMethod(paintComp);

		board.toClass();
		return board;

	}
}
